A Brief Discussion on the Differences Between throw and throw ex
TLDR
- Using
throw;preserves the original Stack Trace, ensuring that the original location of the error can be tracked during debugging. - Using
throw ex;resets the Stack Trace, causing the loss of error stack information; it should be avoided. - If you need to throw a new Exception, pass the original Exception as an
InnerExceptionto preserve the error context. - If a
catchblock contains onlythrow;with no other logic, thetry...catchblock should be removed. - .NET 5 and later versions provide the
CA2200rule, which automatically detects and warns against the improper use ofthrow ex.
Analysis of the Differences Between throw and throw ex
In C# try...catch blocks, there is a significant difference in behavior between throw; and throw ex;.
Using throw to Preserve Stack Information
When does this issue arise: When you need to intercept an exception in a catch block, perform logging or processing, and then re-throw the original exception.
Using throw; re-throws the caught Exception and fully preserves the original Exception stack information. This is crucial for debugging as it provides the correct path where the error occurred.
try {
try {
int result = Divide(1, 0);
} catch {
throw;
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}
static int Divide(int numerator, int denominator) {
return numerator / denominator;
}The execution results show that the error path points correctly to the Divide method on line 3:
System.DivideByZeroException: Attempted to divide by zero.
at Program.<<Main>$>g__Divide|0_0(Int32 numerator, Int32 denominator) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 12
at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 3Using throw ex Causes Stack Loss
When does this issue arise: When developers mistakenly believe that throw ex is the standard way to re-throw an exception, ignoring the fact that it treats the exception object as a "new" source of error.
Using throw ex creates and throws a new Exception, which resets the Exception's stack information, making it difficult to track.
try {
try {
int result = Divide(1, 0);
} catch (Exception ex) {
throw ex;
}
} catch (Exception e) {
Console.WriteLine(e.ToString());
}The execution results show that the error location has changed to the line number where throw ex is located, and the original error path is lost:
System.DivideByZeroException: Attempted to divide by zero.
at Program.<Main>$(String[] args) in D:\Programming\Projects\TestThrow\TestThrow\Program.cs:line 5TIP
Test results show that for an Exception re-thrown using throw ex, its InnerException is null, which confirms that the original stack information is indeed lost.
Best Practices and Recommendations
Correct Exception Wrapping
When does this issue arise: When you need to throw a custom Exception while wanting to preserve the original error information.
We should throw a new Exception and pass the original Exception as the innerException parameter. This allows you to add new context information while retaining the original stack trace.
public class CustomException : Exception {
public CustomException(string message, Exception innerException)
: base(message, innerException) {
}
}
try {
// Code that might throw an Exception...
} catch (Exception ex) {
// ex must be passed as an InnerException to preserve information
throw new CustomException("Additional information", ex);
}Avoid Redundant try...catch
When does this issue arise: When a try...catch block is written in the code, but the catch block only executes throw; without any additional processing.
If there is no processing logic inside the catch block other than throw;, the try...catch block should be removed directly, as it has no positive impact on program execution.
// It is recommended to remove such meaningless catch blocks
try {
// Code that might throw an Exception...
} catch {
throw;
}Change Log
- 2025-07-31 Initial document creation.
